/**
 * Classe permettant la gestion de l'intelligence artificielle d'un ascenseur
 * @authors: Bertrand GUILLAUMOND, Nicolas BOUQUIN
 * @version 1.0
 */


import java.util.*;
import java.lang.*;

public class Elevator{

	final int CAPACITY = 15;
	final int LIMIT = 44;
	final int RDC = 4;
	final int TIME_PER_FLOOR = 5;
	final int ENERGY_UP = 1500;
	final int ENERGY_DOWN = 750;
	
	private int origin;
	private int idleTime;
	private int currentFloor;
	private int destinationFloor;
	private int nbUsers;
	private boolean moves;
	private boolean stopped;
	private ElevatorPanel panel;
	private Vector<Customer> callers = new Vector<Customer>();
	private Vector<Integer> destinations = new Vector<Integer>();
	private Vector<Integer> toRemove = new Vector<Integer>();
	private ConsoleFrame console;
	
	private Vector<Customer> customers = new Vector<Customer>(CAPACITY);
	private Statistics stat;
	
	public Elevator(int floor,ElevatorPanel ep,ConsoleFrame c)
	{
		currentFloor = floor;
		destinationFloor = floor;
		nbUsers = 0;
		moves = false;
		stopped = true;
		panel = ep;
		console = c;
		origin = RDC;
		idleTime = 0;
		stat = new Statistics();
	}

	public Statistics getStatistics()
	{
		return stat;
	}

	public void setPanel(ElevatorPanel p)
	{
		panel = p;
	}

	public void setOrigin(int o)
	{
		origin = o;
	}

	public int getOrigin()
	{
		return origin;
	}
		
	public void goToOrigin()
	{
			forceToGo(origin);	
	}

	public int getFloor(){ return currentFloor; }
	
	public int getNbUsers(){ return nbUsers; }	

	public void goUp(int to)
	{
		destinationFloor = to;
	}
	
	public void goDown(int to)
	{
		destinationFloor = to;
	}

	public boolean canStopTo(int to)
	{
		if((isUpping() && to > currentFloor) || (!isUpping()  && to < currentFloor) || to == currentFloor || isIdle())
			return true;
		else
			return false;
	}

	public void removeFloorOfDestinations(int floor)
	{
		while(destinations.remove(new Integer(floor)))
								continue;	
	}

	public void removeCaller(int position)
	{
		callers.remove(position);
	}

	public void removeCustomer(int position)
	{
		customers.remove(position);
	}

	public void updatePanel(int nbUsers, int floor)
	{
		panel.setNbUsers(nbUsers);	
				
		panel.setFloor(floor);	
	}


	public void updateStat()
	{
		if(isInMove() && !stopped)
		{
			
			int conso = isUpping() ? ENERGY_UP : ENERGY_DOWN;
			String cs = isUpping() ? "upping" : "downing";
			
			//System.out.println("Consommation électrique :"+stat.getConso()+ " " + cs );
			stat.setConso(stat.getConso() + conso);		
		}
	}

	public boolean isIdle()
	{
		return (customers.isEmpty() && callers.isEmpty());
	}

	public boolean isUpping()
	{
		if (isInMove() && currentFloor < destinationFloor)
			return true;
		return false;
	}

	public boolean isGoingToUp()
	{
		return false;
	}

	public int refresh()
	{
		/*if(destinations.size() > 0){
				for(int i = 0; i < destinations.size(); i++)
					console.appendNoBr("\t"+(destinations.get(i) -4));
				console.append("");
		}*/

		if(currentFloor != destinationFloor) // si il est pas arrivé
		{
			moves = true;
			stopped = false;
			if(currentFloor < destinationFloor)
				currentFloor++;
			else
				currentFloor--;

			for(int i = 0;i < callers.size(); i++)
			{
				callers.get(i).incrementWaitTime();
			}
		
			for(int i = 0;i < customers.size(); i++)
			{
				customers.get(i).incrementTravelTime();
			}
			
		}
		else{ // si il est arrivé
			moves = false;
			stopped = true;
		
			if(callers.isEmpty()) // si personne ne l'appelle
			{
				if(customers.isEmpty())
				{
					
					if(idleTime >= 25)
					{
							
							this.goToOrigin();
							//idleTime = 0;
					}else{
						idleTime+=5;
					}
				}
			
			}
			else // si qqun l'appelle
			{
				/*console.append("--> Liste des destinations");
				for(int i = 0; i < destinations.size(); i++)
					console.appendNoBr("\t"+(destinations.get(i) -4));
				console.append("");*/
				
				for(int i = 0;i < callers.size(); i++)
				{
					if(callers.get(i).getCurrentFloor() == currentFloor)
					{
						if(addCustomer(callers.get(i)) != -1)
						{
							
							console.append("Ascenseur arrivé à destination niveau "+(currentFloor -4)  +" , un utilisateur embarque");
							stat.addWaitTime(callers.get(i).getWaitTime());
							//destinations.add(callers.get(i).getDestination());
							
							//console.append("Prochaine destination : "+ (destinationFloor-4));
							toRemove.add(i);
						}
					}
				}
				int count = 0;
				for(int i = 0; i < toRemove.size();i++)
				{
					//System.out.print("\t"+toRemove.get(i));
					removeCaller(toRemove.get(i)-count);
					count++;
				}
				count = 0;
				
				toRemove.clear();
				removeFloorOfDestinations(currentFloor);							
			
								
			} // fin si qqun l'appelle
			
			for (int i = 0; i < customers.size(); i++)
			{
				if(customers.get(i).getDestination() == currentFloor)
				{
					console.append("Utilisateur arrivé à destination, un utilisateur sort niveau "+(currentFloor-4));
					//console.append("L'utilisateur a voyagé : "+customers.get(i).getTravelTime());
					stat.addTravelTime(customers.get(i).getTravelTime());
					removeCustomer(i);					
					
					removeFloorOfDestinations(currentFloor);
								
					//Collections.sort(destinations);
					nbUsers--;
				}	
			}
			if(nbUsers == 0  && callers.size() == 0)
			{
				customers.clear();
				destinations.clear();
			}
			else
			destinationFloor = getNextDestination();
		}
		updatePanel(nbUsers, currentFloor);	
		updateStat();
		return currentFloor;
	}

	public boolean isInMove()
	{
		return moves;
	}

	public boolean isStopped()
	{
		return stopped;
	}

	public void setStopped(boolean s)
	{
		stopped = s;
	}
	
	public void go(int to)
	{
		if(to > 44 || to < 0 || currentFloor == to)
			return;
		if(to > currentFloor)
			this.goUp(to);
		else
		{
			if(to < currentFloor)
				this.goDown(to);
		}
	
		moves = true;
		
		panel.setDestinationString("Vers E" + (to - 4)+".");
		panel.setDestination(to);
		
		if(!callers.isEmpty())
			panel.setCalled(true);
		else
			panel.setCalled(false);
		
	}
	
	public boolean isEmpty()
	{
		return nbUsers == 0;
	}
	
	public boolean isFull()
	{
		return nbUsers == CAPACITY;
	}
	
	public float getTimeTo(int to)
	{
		return Math.abs(currentFloor - to) * TIME_PER_FLOOR;
	}
	
	public float getEnergyTo(int to)
	{
		float energy = 0;
		if(to > currentFloor)
		{
			energy = ENERGY_UP * (Math.abs(currentFloor - to));
		}
		else
		{
			energy = ENERGY_DOWN * (Math.abs(currentFloor - to));
		}
		
		return energy;
	}

	public int getDistanceTo(int to)
	{
		return Math.abs(currentFloor - to);
	}
	
	public int addCustomer(Customer c)
	{
		if(nbUsers < CAPACITY){
			customers.add(c);
			destinations.add(c.getDestination());
			if(needSort(c.getDestination()))			
				Collections.sort(destinations);
			nbUsers++;
			return nbUsers;
		}
		else{
			return -1;
		}
	}

	public boolean needSort(int dest)
	{
		boolean ret;		
		if((isUpping() && dest >= Collections.min(destinations)) || (!isUpping() && dest <= Collections.max(destinations)))
		{
			//console.append("Destination "+(dest -4) +" n'est pas entre Entre "+(Collections.min(destinations) -4) +"et "+ (Collections.max(destinations) -4)+"pas de tri à faire" )	;		
			ret =  true;
		}		
		else
		{
			//console.append("Destination "+dest +" est entre Entre "+Collections.min(destinations) +"et "+ Collections.max(destinations)+" tri à faire"  )	;
			ret = false;
		}
		
		return ret;
	}
	
	
	public int getNextDestination()
	{
		if(destinations.isEmpty())
			return currentFloor;

        Collections.sort(destinations);
			
		if(destinations.get(0) < currentFloor)
		{
			if(destinations.size() > 1 && (destinations.get(0) < destinations.get(1)))
			    Collections.reverse(destinations);
		}
        else
        {
            if(destinations.size() > 1 && (destinations.get(0) > destinations.get(1)))
			     Collections.reverse(destinations);
        }
		return destinations.get(0);
	}

	public int getCurrentDestination()
	{
		if(destinations.size() > 0)
			return destinations.get(0);
		else return 0;
	}
	
	public Vector<Customer> getCallers()
	{
		return callers;
	}

	public Vector<Customer> getCustomers()
	{
		return customers;
	}


	public void addDestination(int floor)
	{
		destinations.add(floor);
	}
	
	public void addCaller(Customer c)
	{
		if(callers.size() == 0)
			destinationFloor = c.getCurrentFloor();
		callers.add(c);
		
		this.addDestination(c.getCurrentFloor());
		destinationFloor = getNextDestination();

		console.append("Ascenseur appelé niveau "+(c.getCurrentFloor() -4)  +" , un utilisateur embarque");
	}

	public void forceToGo(int to)
	{
		callers.clear();
		for (int i = 0; i < customers.size(); i++)
		{
			customers.get(i).setDestination(to);			
		}
		//console.append("Ascenseur forcé à décharger au niveau "+to);
		destinations.clear();
		
		destinationFloor = to;		
	}
}
